import shutil
import sys
import os
import json
import time
import uuid
import random
import redis
from datetime import datetime, timedelta
from PySide6.QtWidgets import (
    QApplication,
    QMainWindow,
    QTabWidget,
    QWidget,
    QVBoxLayout,
    QHBoxLayout,
    QLabel,
    QPushButton,
    QTextEdit,
    QTreeWidget,
    QTreeWidgetItem,
    QComboBox,
    QMessageBox,
    QSplitter,
    QFrame,
    QListWidget,
    QListWidgetItem,
    QToolBar,
    QInputDialog,
    QMenu,
    QDialog,
    QFormLayout,
    QGroupBox,
    QProgressBar,
    QTableWidget,
    QTableWidgetItem,
    QHeaderView,
    QFileDialog,
    QLineEdit,
    QSpinBox,
    QCheckBox,
    QDialogButtonBox,
    QTextBrowser,
)
from PySide6.QtCore import Qt, QThread, Signal, Slot, QDateTime, QTimer, QObject
from PySide6.QtGui import QFont, QColor, QIcon, QAction
import requests


# 数据状态枚举
class AnnotationStatus:
    UNASSIGNED = "未分配"
    ASSIGNED = "已分配"
    COMPLETED = "标注完成"
    REVIEWED = "检查完成"
    ERROR = "检查错误"


# 用户角色枚举
class UserRole:
    ANNOTATOR = "标注员"
    EMEND = "检查员"
    ADMIN = "管理员"


def _parse_time(time_str):
    """解析时间字符串为datetime对象"""
    if not time_str:
        return None
    try:
        if "+" in time_str or "Z" in time_str:
            return datetime.fromisoformat(time_str.replace("Z", "+00:00"))
        else:
            return datetime.fromisoformat(time_str)
    except:
        return None


class RedisConnector(QObject):
    """Redis连接类"""

    connected = Signal(bool)
    error_occurred = Signal(str)
    connection_checked = Signal(bool)

    def __init__(self, host="localhost", port=6379, password="123456", db=0):
        super().__init__()
        self.host = host
        self.port = port
        self.password = password
        self.db = db
        self.client = None
        self.connected_status = False

    def connect(self):
        """连接到Redis服务器"""
        try:
            self.client = redis.Redis(
                host=self.host,
                port=self.port,
                password=self.password,
                db=self.db,
                decode_responses=True,
                socket_timeout=1,
            )
            # self.client.select(self.db)
            # 测试连接
            if self.client.ping():
                self.connected_status = True
                self.connected.emit(True)
                self.connection_checked.emit(True)
                return True
            else:
                self.connected_status = False
                self.connected.emit(False)
                self.error_occurred.emit("无法ping通Redis服务器")
                self.connection_checked.emit(False)
                return False
        except Exception as e:
            self.connected_status = False
            self.connected.emit(False)
            self.error_occurred.emit(f"连接Redis出错: {str(e)}")
            self.connection_checked.emit(False)
            return False

    def is_connected(self):
        """检查是否已连接"""
        if not self.client:
            return False
        try:
            return self.client.ping()
        except:
            return False

    def get_tabs(
        self,
    ):
        """获取用户的tab列表"""
        if not self.is_connected():
            return []
        try:
            return [
                i.replace("user:tabs:", "")
                for i in list(self.client.keys(f"user:tabs:*"))
            ]
        except Exception as e:
            self.error_occurred.emit(f"获取用户tab列表出错: {str(e)}")
            return []

    def get_tab(self, tab):
        """获取用户的tab列表"""
        if not self.is_connected():
            return {}
        try:
            tab = self.client.hgetall(f"user:tabs:{tab}")
            if "labels" in tab:
                tab["labels"] = json.loads(tab["labels"])
            if "user_metadata" in tab:
                tab["user_metadata"] = json.loads(tab["user_metadata"])
            return tab
        except Exception as e:
            self.error_occurred.emit(f"获取用户tab列表出错: {str(e)}")
            return {}

    def save_tab_data(self, tab, data):
        """保存文件的标注数据"""
        if not self.is_connected():
            return False
        try:
            self.client.hset(f"user:tabs:{tab}", mapping=data)
            self.client.hset(
                f"user:tabs:{tab}", "updatetime", datetime.now().isoformat()
            )
            return True
        except Exception as e:
            self.error_occurred.emit(f"保存文件数据出错: {str(e)}")
            return False

    def get_models(
        self,
    ):
        """获取模型列表"""
        if not self.is_connected():
            return []
        try:
            models = []
            for model_name in list(self.client.keys("models:*")):
                model_args = self.client.hgetall(model_name)
                models.append(model_args)
            return models
        except Exception as e:
            print(e)
            self.error_occurred.emit(f"获取用户tab列表出错: {str(e)}")
            return []

    def get_tab_files(self, tab_id, status=None, username=None, roles=None):
        """获取tab下的文件列表，可按状态和用户筛选"""
        if not self.is_connected():
            return []
        try:
            all_files = list(self.client.smembers(f"tab:{tab_id}:files"))
            # 筛选用户的文件
            if username:
                user_files = []
                for filename in all_files:
                    file_data = self.get_file_data(filename)
                    owner = file_data.get("file_attributes", {}).get("owner", "")
                    if owner == username:
                        user_files.append(filename)
                all_files = user_files

            # 按状态筛选
            if status:
                filtered_files = []
                for filename in all_files:
                    file_data = self.get_file_data(filename)
                    file_status = file_data.get("file_attributes", {}).get("status", "")
                    if file_status == status:
                        filtered_files.append(filename)
                return filtered_files

            return all_files
        except Exception as e:
            self.error_occurred.emit(f"获取tab文件列表出错: {str(e)}")
            return []

    def get_file_data(self, filename):
        """获取文件的标注数据"""
        if not self.is_connected():
            return None
        try:
            file_data = self.client.hgetall(f"file:{filename}")
            if "regions" in file_data:
                file_data["regions"] = json.loads(file_data["regions"])
            if "file_attributes" in file_data:
                file_data["file_attributes"] = json.loads(file_data["file_attributes"])
            return file_data
        except Exception as e:
            self.error_occurred.emit(f"获取文件数据出错: {str(e)}")
            return None

    def save_file_data(self, filename, data):
        """保存文件的标注数据"""
        if not self.is_connected():
            return False
        try:
            data_to_save = data.copy()
            if "file_attributes" in data_to_save:
                data_to_save["file_attributes"] = json.dumps(
                    data_to_save["file_attributes"]
                )
            if "regions" in data_to_save:
                data_to_save["regions"] = json.dumps(data_to_save["regions"])
            self.client.hset(f"file:{filename}", mapping=data_to_save)
            self.client.hset(
                f"file:{filename}", "updatetime", datetime.now().isoformat()
            )
            return True
        except Exception as e:
            self.error_occurred.emit(f"保存文件数据出错: {str(e)}")
            return False

    def update_file_status(self, filename, status):
        """更新文件状态"""
        if not self.is_connected():
            return False
        try:
            self.client.hset(f"file:{filename}", "status", status)
            self.client.hset(
                f"file:{filename}", "updatetime", datetime.now().isoformat()
            )
            return True
        except Exception as e:
            self.error_occurred.emit(f"更新文件状态出错: {str(e)}")
            return False

    def get_user_role(self, user_id):
        """获取用户角色"""
        if not self.is_connected():
            return UserRole.ANNOTATOR
        try:
            return self.client.hget(f"user:{user_id}", "role") or UserRole.ANNOTATOR
        except Exception as e:
            self.error_occurred.emit(f"获取用户角色出错: {str(e)}")
            return UserRole.ANNOTATOR

    def add_tab(self, tab_id, data_to_save):
        """为用户添加tab"""
        if not self.is_connected():
            return False
        try:
            self.client.hset(f"user:tabs:{tab_id}", mapping=data_to_save)
            return True
        except Exception as e:
            self.error_occurred.emit(f"添加用户tab出错: {str(e)}")
            return False

    def add_file_to_tab(self, tab_id, filename):
        """将文件添加到tab"""
        if not self.is_connected():
            return False
        try:
            self.client.sadd(f"tab:{tab_id}:files", filename)
            return True
        except Exception as e:
            self.error_occurred.emit(f"添加文件到tab出错: {str(e)}")
            return False

    def delete_tab(self, user_id, tab_id):
        """删除Tab及其关联的文件"""
        if not self.is_connected():
            return False
        try:
            # 获取tab下的所有文件
            files = self.get_tab_files(tab_id)

            # 删除文件数据
            for filename in files:
                self.client.delete(f"file:{filename}")

            # 从用户的tab列表中删除该tab
            self.client.srem(f"user:{user_id}:tabs", tab_id)

            # 删除tab的文件列表
            self.client.delete(f"tab:{tab_id}:files")

            return True
        except Exception as e:
            self.error_occurred.emit(f"删除tab出错: {str(e)}")
            return False

    def assign_file_to_user(self, filename, user_id):
        """将文件分配给用户"""
        if not self.is_connected():
            return False
        try:
            # 更新文件所有者和状态
            self.client.hset(f"file:{filename}", "owner", user_id)
            self.client.hset(f"file:{filename}", "status", AnnotationStatus.ASSIGNED)
            self.client.hset(
                f"file:{filename}", "updatetime", datetime.now().isoformat()
            )
            return True
        except Exception as e:
            self.error_occurred.emit(f"分配文件出错: {str(e)}")
            return False

    def get_unassigned_files(self, tab_id):
        """获取未分配的文件"""
        if not self.is_connected():
            return []
        try:
            return self.get_tab_files(tab_id, AnnotationStatus.UNASSIGNED)
        except Exception as e:
            self.error_occurred.emit(f"获取未分配文件出错: {str(e)}")
            return []


class LocalDataManager(QObject):
    """本地数据管理类"""

    data_loaded = Signal(dict)
    data_saved = Signal(str)
    error_occurred = Signal(str)

    def __init__(self, username=None, root_dir="./local_data"):
        super().__init__()
        self.username = username
        self.root_dir = os.path.join(root_dir, username)
        self._ensure_directories()

    def _ensure_directories(self):
        """确保用户数据目录存在"""
        os.makedirs(self.root_dir, exist_ok=True)

    def get_local_tabs(self):
        """获取本地所有tab"""
        try:
            return [
                d
                for d in os.listdir(self.root_dir)
                if os.path.isdir(os.path.join(self.root_dir, d))
            ]
        except Exception as e:
            self.error_occurred.emit(f"获取本地tab列表出错: {str(e)}")
            return []

    def check_updatetime(self, new_data, old_data):
        """获取文件的本地数据"""
        try:
            new_time = _parse_time(
                new_data["updatetime"],
            )
            old_time = _parse_time(
                old_data["updatetime"],
            )
            if new_time and old_time and new_time > old_time:
                return True
            if old_time:
                return True
            return False
        except Exception as e:
            self.error_occurred.emit(f"获取本地文件数据出错: {str(e)}")
            return None

    def get_tab_files(self, tab_id, status=None, username=None):
        """获取tab下的文件列表，可按状态和用户筛选"""
        try:
            tab_dir = os.path.join(self.root_dir, tab_id)
            if not os.path.exists(tab_dir):
                return []

            all_files = [
                f
                for f in os.listdir(tab_dir)
                if f.endswith(".jpg") or f.endswith(".png")
            ]

            # 筛选用户的文件
            if username:
                usernames = [username]
                if isinstance(username, list):
                    usernames = username
                else:
                    usernames = [username]
                user_files = []
                for filename in all_files:
                    file_data = self.get_file_data(tab_id, filename)
                    if file_data.get("file_attributes",{}).get("owner","") in usernames:
                        user_files.append(filename)
                all_files = user_files

            # 按状态筛选
            if status:
                filtered_files = []
                for filename in all_files:
                    file_data = self.get_file_data(tab_id, filename)
                    if file_data.get("status") == status:
                        filtered_files.append(filename)
                return filtered_files
            return all_files
        except Exception as e:
            self.error_occurred.emit(f"获取本地文件列表出错: {str(e)}")
            return []

    def get_tab_file(self, tab_id):
        """获取tab下的文件列表，可按状态和用户筛选"""
        try:
            tab_dir = os.path.join(self.root_dir, tab_id, ".info")
            if not os.path.exists(tab_dir):
                return {}
            with open(tab_dir, "r", encoding="utf-8"
            ) as f:
                tab = json.load(f)
            return tab
        except Exception as e:
            self.error_occurred.emit(f"获取{tab_id}的 tab 出错: {str(e)}")
            return {}

    def get_file_data(self, tab_id, filename):
        """获取文件的本地数据"""
        try:
            basename = os.path.splitext(filename)[0]
            file_path = os.path.join(self.root_dir, tab_id, f"{basename}.json")
            if not os.path.exists(file_path):
                return None
            with open(file_path, "r", encoding="utf-8") as f:
                return json.load(f)
        except Exception as e:
            self.error_occurred.emit(f"获取本地文件数据出错: {str(e)}")
            return None

    def save_file_data(self, tab_id, filename, data):
        """保存文件的本地数据"""
        try:
            tab_dir = os.path.join(self.root_dir, tab_id)
            os.makedirs(tab_dir, exist_ok=True)
            basename = os.path.splitext(filename)[0]
            ann_file_path = os.path.join(tab_dir, f"{basename}.json")
            file_path = os.path.join(tab_dir, filename)
            old_data = self.get_file_data(tab_id, filename)
            if self.check_updatetime(data, old_data):
                if "updatetime" not in data:
                    data["updatetime"] = datetime.now().isoformat()
                with open(ann_file_path, "w", encoding="utf-8") as f:
                    json.dump(data, f, ensure_ascii=False, indent=2)
            self.data_saved.emit(file_path)
            return True
        except Exception as e:
            self.error_occurred.emit(f"保存本地文件数据出错: {str(e)}")
            return False

    def save_local_tab(self, tab_id, tab_data):
        """添加本地tab"""
        try:
            tab_file = os.path.join(self.root_dir, tab_id, ".info")
            with open(tab_file, "w", encoding="utf-8") as f:
                json.dump(tab_data, f, ensure_ascii=False, indent=2)
            return True
        except Exception as e:
            self.error_occurred.emit(f"添加本地tab出错: {str(e)}")
            return False

    def add_local_tab(self, tab_id):
        """添加本地tab"""
        try:
            tab_dir = os.path.join(self.root_dir, tab_id)
            os.makedirs(tab_dir, exist_ok=True)
            return True
        except Exception as e:
            self.error_occurred.emit(f"添加本地tab出错: {str(e)}")
            return False

    def delete_local_tab(self, tab_id):
        """删除本地tab及其所有文件"""
        try:
            tab_dir = os.path.join(self.root_dir, tab_id)
            if not os.path.exists(tab_dir):
                return True

            # 删除目录下的所有文件
            for filename in os.listdir(tab_dir):
                file_path = os.path.join(tab_dir, filename)
                if os.path.isfile(file_path):
                    os.remove(file_path)

            # 删除目录
            os.rmdir(tab_dir)
            return True
        except Exception as e:
            self.error_occurred.emit(f"删除本地tab出错: {str(e)}")
            return False

    def assign_file_to_user(self, tab_id, filename, user_id):
        """将文件分配给用户（本地）"""
        try:
            file_data = self.get_file_data(tab_id, filename)
            if not file_data:
                return False

            file_data["owner"] = user_id
            file_data["status"] = AnnotationStatus.ASSIGNED
            file_data["updatetime"] = datetime.now().isoformat()

            return self.save_file_data(tab_id, filename, file_data)
        except Exception as e:
            self.error_occurred.emit(f"本地分配文件出错: {str(e)}")
            return False

    def get_unassigned_files(self, tab_id):
        """获取本地未分配的文件"""
        try:
            return self.get_tab_files(tab_id, AnnotationStatus.UNASSIGNED)
        except Exception as e:
            self.error_occurred.emit(f"获取本地未分配文件出错: {str(e)}")
            return []


class DataSyncWorker(QThread):
    """数据同步工作线程"""

    sync_progress = Signal(int)
    sync_complete = Signal(bool, str)
    file_synced = Signal(str, str)  # 文件ID, 状态

    def __init__(self, user_id, redis_connector, local_manager):
        super().__init__()
        self.username = user_id
        self.redis = redis_connector
        self.local = local_manager
        self.tabs_to_sync = []
        self.stop_requested = False

    def set_tabs_to_sync(self, tabs):
        """设置要同步的tab列表"""
        self.tabs_to_sync = tabs

    def run(self):
        """执行数据同步"""
        try:
            total_files = 0
            synced_files = 0
            for tab_id in self.tabs_to_sync:
                redis_files = (
                    self.redis.get_tab_files(tab_id)
                    if self.redis.is_connected()
                    else []
                )
                local_files = self.local.get_tab_files(tab_id)
                total_files += len(redis_files) + len(local_files)
            self.sync_progress.emit(0)

            for tab_id in self.tabs_to_sync:
                if self.stop_requested:
                    self.sync_complete.emit(False, "同步被用户取消")
                    return

                # 同步Redis到本地
                if self.redis.is_connected():
                    redis_files = self.redis.get_tab_files(tab_id)
                    print(redis_files)
                    for filename in redis_files:
                        if self.stop_requested:
                            self.sync_complete.emit(False, "同步被用户取消")
                            return

                        redis_data = self.redis.get_file_data(filename)
                        local_data = self.local.get_file_data(tab_id, filename)
                        print(filename, local_data)

                        redis_time = self._parse_time(redis_data.get("updatetime"))
                        local_time = self._parse_time(
                            local_data.get("updatetime") if local_data else None
                        )

                        if not local_data or (
                            redis_time and local_time and redis_time > local_time
                        ):
                            if redis_data:
                                self.local.save_file_data(tab_id, filename, redis_data)
                                self.file_synced.emit(filename, "从Redis同步到本地")

                        synced_files += 1
                        progress = (
                            int((synced_files / total_files) * 100)
                            if total_files > 0
                            else 100
                        )
                        self.sync_progress.emit(progress)

                # 同步本地到Redis
                if self.redis.is_connected():
                    local_files = self.local.get_tab_files(tab_id)
                    for filename in local_files:
                        if self.stop_requested:
                            self.sync_complete.emit(False, "同步被用户取消")
                            return

                        local_data = self.local.get_file_data(tab_id, filename)
                        redis_data = self.redis.get_file_data(filename)

                        local_time = self._parse_time(local_data.get("updatetime"))
                        redis_time = self._parse_time(
                            redis_data.get("updatetime") if redis_data else None
                        )

                        if local_data and (
                            not redis_data
                            or local_time
                            and redis_time
                            and local_time > redis_time
                        ):
                            if self.redis.save_file_data(filename, local_data):
                                self.redis.add_file_to_tab(tab_id, filename)
                                self.file_synced.emit(filename, "从本地同步到Redis")

                        synced_files += 1
                        progress = (
                            int((synced_files / total_files) * 100)
                            if total_files > 0
                            else 100
                        )
                        self.sync_progress.emit(progress)

            self.sync_complete.emit(True, f"成功同步 {synced_files} 个文件")
        except Exception as e:
            self.sync_complete.emit(False, f"同步过程中出错: {str(e)}")

    def stop_sync(self):
        """停止同步过程"""
        self.stop_requested = True

    def _parse_time(self, time_str):
        """解析时间字符串为datetime对象"""
        if not time_str:
            return None
        try:
            if "+" in time_str or "Z" in time_str:
                return datetime.fromisoformat(time_str.replace("Z", "+00:00"))
            else:
                return datetime.fromisoformat(time_str)
        except:
            return None


class AddFilesDialog(QDialog):
    """添加文件对话框"""

    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("添加随机文件")
        self.setMinimumWidth(400)

        layout = QVBoxLayout(self)

        form_layout = QFormLayout()

        self.file_count = QSpinBox()
        self.file_count.setMinimum(1)
        self.file_count.setMaximum(100)
        self.file_count.setValue(10)
        form_layout.addRow("文件数量:", self.file_count)

        self.include_unassigned = QCheckBox("包含未分配")
        self.include_unassigned.setChecked(True)
        form_layout.addRow(self.include_unassigned)

        self.include_assigned = QCheckBox("包含已分配")
        self.include_assigned.setChecked(True)
        form_layout.addRow(self.include_assigned)

        self.include_completed = QCheckBox("包含标注完成")
        self.include_completed.setChecked(True)
        form_layout.addRow(self.include_completed)

        self.include_reviewed = QCheckBox("包含检查完成")
        self.include_reviewed.setChecked(True)
        form_layout.addRow(self.include_reviewed)

        self.include_error = QCheckBox("包含检查错误")
        self.include_error.setChecked(True)
        form_layout.addRow(self.include_error)

        layout.addLayout(form_layout)

        buttons_layout = QHBoxLayout()
        self.ok_button = QPushButton("确定")
        self.ok_button.clicked.connect(self.accept)
        self.cancel_button = QPushButton("取消")
        self.cancel_button.clicked.connect(self.reject)

        buttons_layout.addWidget(self.ok_button)
        buttons_layout.addWidget(self.cancel_button)

        layout.addLayout(buttons_layout)

    def get_options(self):
        """获取用户选择的选项"""
        options = {"file_count": self.file_count.value(), "status_options": []}

        if self.include_unassigned.isChecked():
            options["status_options"].append(AnnotationStatus.UNASSIGNED)
        if self.include_assigned.isChecked():
            options["status_options"].append(AnnotationStatus.ASSIGNED)
        if self.include_completed.isChecked():
            options["status_options"].append(AnnotationStatus.COMPLETED)
        if self.include_reviewed.isChecked():
            options["status_options"].append(AnnotationStatus.REVIEWED)
        if self.include_error.isChecked():
            options["status_options"].append(AnnotationStatus.ERROR)

        return options


class AssignFilesDialog(QDialog):
    """分配文件对话框"""

    def __init__(
        self,
        parent=None,
        unassigned_files=None,
        tab_id=None,
        user_id=None,
        redis_connector=None,
        local_manager=None,
        redis_available=True,
    ):
        super().__init__(parent)
        self.setWindowTitle("分配文件")
        self.setMinimumWidth(400)

        self.unassigned_files = unassigned_files or []
        self.tab_id = tab_id
        self.username = user_id
        self.redis = redis_connector
        self.local = local_manager
        self.redis_available = redis_available

        self.selected_files = []

        layout = QVBoxLayout(self)

        if not self.unassigned_files:
            layout.addWidget(QLabel("没有可分配的未标注文件"))
            self.button_box = QDialogButtonBox(QDialogButtonBox.Cancel)
            self.button_box.rejected.connect(self.reject)
            layout.addWidget(self.button_box)
            return

        layout.addWidget(QLabel("选择要分配给自己的文件:"))

        self.file_list = QListWidget()
        for filename in self.unassigned_files:
            if self.redis_available:
                file_data = self.redis.get_file_data(filename)
            else:
                file_data = self.local.get_file_data(self.tab_id, filename)

            file_name = file_data.get("file_name", filename)
            item = QListWidgetItem(f"{filename} - {file_name}")
            item.setData(Qt.UserRole, filename)
            self.file_list.addItem(item)

        layout.addWidget(self.file_list)

        self.select_all_checkbox = QCheckBox("全选")
        self.select_all_checkbox.stateChanged.connect(self.toggle_select_all)
        layout.addWidget(self.select_all_checkbox)

        self.button_box = QDialogButtonBox(
            QDialogButtonBox.Ok | QDialogButtonBox.Cancel
        )
        self.button_box.accepted.connect(self.accept_selection)
        self.button_box.rejected.connect(self.reject)
        layout.addWidget(self.button_box)

    def toggle_select_all(self, state):
        """全选/取消全选"""
        for i in range(self.file_list.count()):
            item = self.file_list.item(i)
            if state == Qt.Checked:
                item.setCheckState(Qt.Checked)
            else:
                item.setCheckState(Qt.Unchecked)

    def accept_selection(self):
        """接受选择"""
        self.selected_files = []
        for i in range(self.file_list.count()):
            item = self.file_list.item(i)
            if item.checkState() == Qt.Checked:
                self.selected_files.append(item.data(Qt.UserRole))

        if not self.selected_files:
            QMessageBox.information(self, "提示", "请选择要分配的文件")
            return

        # 分配文件
        success_count = 0
        for filename in self.selected_files:
            if self.redis_available:
                success = self.redis.assign_file_to_user(filename, self.username)
            else:
                success = self.local.assign_file_to_user(
                    self.tab_id, filename, self.username
                )

            if success:
                success_count += 1

        if success_count > 0:
            QMessageBox.information(self, "成功", f"成功分配 {success_count} 个文件")
            self.accept()
        else:
            QMessageBox.warning(self, "错误", "分配文件失败")
            self.reject()


class AddReviewDialog(QDialog):
    """添加审核评论对话框"""

    def __init__(self, parent=None, current_status=None):
        super().__init__(parent)
        self.setWindowTitle("添加审核意见")
        self.setMinimumWidth(400)

        self.current_status = current_status
        self.selected_status = current_status

        layout = QVBoxLayout(self)

        form_layout = QFormLayout()

        self.status_combo = QComboBox()
        # 审核员只能设置为标注完成或检查错误
        self.status_combo.addItems([AnnotationStatus.COMPLETED, AnnotationStatus.ERROR])
        if current_status in [AnnotationStatus.COMPLETED, AnnotationStatus.ERROR]:
            self.status_combo.setCurrentText(current_status)
        form_layout.addRow("设置状态:", self.status_combo)

        self.comment_text = QTextEdit()
        self.comment_text.setPlaceholderText("输入审核意见...")
        form_layout.addRow("审核意见:", self.comment_text)

        layout.addLayout(form_layout)

        buttons_layout = QHBoxLayout()
        self.ok_button = QPushButton("提交")
        self.ok_button.clicked.connect(self.accept)
        self.cancel_button = QPushButton("取消")
        self.cancel_button.clicked.connect(self.reject)

        buttons_layout.addWidget(self.ok_button)
        buttons_layout.addWidget(self.cancel_button)

        layout.addLayout(buttons_layout)

    def get_review_data(self):
        """获取审核数据"""
        return {
            "status": self.status_combo.currentText(),
            "comment": self.comment_text.toPlainText(),
        }


class EditLabelDialog(QDialog):
    """编辑标签对话框"""

    def __init__(self, parent=None, current_label=None, coordinates=None):
        super().__init__(parent)
        self.setWindowTitle("编辑标签")
        self.setMinimumWidth(400)

        self.current_label = current_label
        self.coordinates = coordinates

        layout = QVBoxLayout(self)

        form_layout = QFormLayout()

        self.label_edit = QLineEdit()
        self.label_edit.setText(current_label)
        form_layout.addRow("标签名称:", self.label_edit)

        if coordinates:
            coord_group = QGroupBox("坐标")
            coord_layout = QFormLayout()

            self.x_edit = QLineEdit(str(coordinates.get("x", 0)))
            self.y_edit = QLineEdit(str(coordinates.get("y", 0)))
            self.width_edit = QLineEdit(str(coordinates.get("width", 0)))
            self.height_edit = QLineEdit(str(coordinates.get("height", 0)))

            coord_layout.addRow("X:", self.x_edit)
            coord_layout.addRow("Y:", self.y_edit)
            coord_layout.addRow("宽度:", self.width_edit)
            coord_layout.addRow("高度:", self.height_edit)

            coord_group.setLayout(coord_layout)
            form_layout.addRow(coord_group)

        layout.addLayout(form_layout)

        buttons_layout = QHBoxLayout()
        self.ok_button = QPushButton("保存")
        self.ok_button.clicked.connect(self.accept)
        self.cancel_button = QPushButton("取消")
        self.cancel_button.clicked.connect(self.reject)

        buttons_layout.addWidget(self.ok_button)
        buttons_layout.addWidget(self.cancel_button)

        layout.addLayout(buttons_layout)

    def get_label_data(self):
        """获取标签数据"""
        label = self.label_edit.text()

        if not self.coordinates:
            return {"label": label}

        try:
            x = int(self.x_edit.text())
            y = int(self.y_edit.text())
            width = int(self.width_edit.text())
            height = int(self.height_edit.text())

            return {
                "label": label,
                "coordinates": {"x": x, "y": y, "width": width, "height": height},
            }
        except ValueError:
            QMessageBox.warning(self, "错误", "坐标值必须为整数")
            return None


class AnnotationApp(QMainWindow):
    """标注应用主窗口"""

    def __init__(self, user_id, user_role):
        super().__init__()
        self.username = user_id
        self.user_role = user_role
        self.setWindowTitle(f"标注工具 - 用户: {user_id} ({user_role})")
        self.resize(1200, 800)

        self.redis = RedisConnector()
        self.local = LocalDataManager(user_id)
        self.sync_worker = None
        self.redis_connection_available = False

        self.redis.connected.connect(self.on_redis_connected)
        self.redis.error_occurred.connect(self.on_error)
        self.local.error_occurred.connect(self.on_error)
        self.redis.connection_checked.connect(self.on_connection_checked)

        self.init_ui()
        self.connect_to_redis()

    def init_ui(self):
        """初始化用户界面"""
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)

        self.create_toolbar()
        self.statusBar().showMessage("就绪")

        main_splitter = QSplitter(Qt.Vertical)
        self.create_file_list_widget()
        self.create_annotation_editor_widget()
        main_splitter.addWidget(self.file_list_widget)
        main_splitter.addWidget(self.annotation_editor_widget)
        main_splitter.setSizes([300, 500])

        main_layout.addWidget(main_splitter)

    def create_toolbar(self):
        """创建工具栏"""
        toolbar = QToolBar("主工具栏")
        self.addToolBar(toolbar)

        connect_action = QAction("连接Redis", self)
        connect_action.triggered.connect(self.connect_to_redis)
        toolbar.addAction(connect_action)

        sync_action = QAction("同步数据", self)
        sync_action.triggered.connect(self.sync_data)
        toolbar.addAction(sync_action)

        self.sync_status_label = QLabel("")
        toolbar.addWidget(self.sync_status_label)

        toolbar.addSeparator()

        # 根据用户角色显示不同的工具栏按钮
        if self.user_role == UserRole.ADMIN:
            add_tab_action = QAction("添加Tab", self)
            add_tab_action.triggered.connect(self.add_new_tab)
            toolbar.addAction(add_tab_action)

            add_files_action = QAction("添加文件", self)
            add_files_action.triggered.connect(self.add_random_files)
            toolbar.addAction(add_files_action)

            delete_tab_action = QAction("删除Tab", self)
            delete_tab_action.triggered.connect(self.delete_current_tab)
            toolbar.addAction(delete_tab_action)
        elif self.user_role == UserRole.ANNOTATOR:
            assign_action = QAction("分配文件", self)
            assign_action.triggered.connect(self.assign_files)
            toolbar.addAction(assign_action)
        elif self.user_role == UserRole.EMEND:
            review_action = QAction("添加审核", self)
            review_action.triggered.connect(self.add_review)
            toolbar.addAction(review_action)

        toolbar.addSeparator()

        toolbar.addWidget(QLabel("状态筛选:"))
        self.status_filter = QComboBox()

        # 根据用户角色显示不同的状态筛选选项
        if self.user_role == UserRole.ANNOTATOR:
            self.status_filter.addItems(
                ["全部", AnnotationStatus.ASSIGNED, AnnotationStatus.COMPLETED]
            )
        elif self.user_role == UserRole.EMEND:
            self.status_filter.addItems(
                [
                    "全部",
                    AnnotationStatus.COMPLETED,
                    AnnotationStatus.REVIEWED,
                    AnnotationStatus.ERROR,
                ]
            )
        else:  # ADMIN
            self.status_filter.addItems(
                [
                    "全部",
                    AnnotationStatus.UNASSIGNED,
                    AnnotationStatus.ASSIGNED,
                    AnnotationStatus.COMPLETED,
                    AnnotationStatus.REVIEWED,
                    AnnotationStatus.ERROR,
                ]
            )

        self.status_filter.currentTextChanged.connect(self.filter_files_by_status)
        toolbar.addAction(QAction("刷新", self, triggered=self.refresh_files))
        toolbar.addWidget(self.status_filter)

    def create_file_list_widget(self):
        """创建文件列表部件"""
        self.file_list_widget = QFrame()
        self.file_list_widget.setFrameShape(QFrame.StyledPanel)
        layout = QVBoxLayout(self.file_list_widget)

        tab_layout = QHBoxLayout()
        tab_layout.addWidget(QLabel("选择Tab:"))

        self.tab_combobox = QComboBox()
        self.tab_combobox.currentTextChanged.connect(self.on_tab_selected)
        tab_layout.addWidget(self.tab_combobox)

        refresh_tab_btn = QPushButton("刷新Tab")
        refresh_tab_btn.clicked.connect(self.refresh_tabs)
        tab_layout.addWidget(refresh_tab_btn)

        # 根据用户角色显示不同的按钮
        if self.user_role == UserRole.ADMIN:
            add_tab_btn = QPushButton("添加Tab")
            add_tab_btn.clicked.connect(self.add_new_tab)
            tab_layout.addWidget(add_tab_btn)

            delete_tab_btn = QPushButton("删除Tab")
            delete_tab_btn.clicked.connect(self.delete_current_tab)
            tab_layout.addWidget(delete_tab_btn)
        elif self.user_role == UserRole.ANNOTATOR:
            assign_btn = QPushButton("分配文件")
            assign_btn.clicked.connect(self.assign_files)
            tab_layout.addWidget(assign_btn)
        elif self.user_role == UserRole.EMEND:
            review_btn = QPushButton("添加审核")
            review_btn.clicked.connect(self.add_review)
            tab_layout.addWidget(review_btn)

        layout.addLayout(tab_layout)

        self.file_table = QTableWidget()
        self.file_table.setColumnCount(5)
        self.file_table.setHorizontalHeaderLabels(
            ["文件ID", "文件名", "状态", "更新时间", "来源"]
        )
        self.file_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.file_table.setSelectionBehavior(QTableWidget.SelectRows)
        self.file_table.cellClicked.connect(self.on_file_selected)
        layout.addWidget(self.file_table)

    def create_annotation_editor_widget(self):
        """创建标注编辑部件"""
        self.annotation_editor_widget = QFrame()
        self.annotation_editor_widget.setFrameShape(QFrame.StyledPanel)
        layout = QVBoxLayout(self.annotation_editor_widget)

        info_layout = QHBoxLayout()
        self.file_info_label = QLabel("未选择文件")
        info_layout.addWidget(self.file_info_label)
        # 根据用户角色显示不同的状态控件
        if self.user_role == UserRole.ANNOTATOR:
            info_layout.addWidget(QLabel("状态:"))
            self.status_combobox = QComboBox()
            self.status_combobox.addItems(
                [AnnotationStatus.ASSIGNED, AnnotationStatus.COMPLETED]
            )
            self.status_combobox.currentTextChanged.connect(self.on_status_changed)
            info_layout.addWidget(self.status_combobox)
        elif self.user_role == UserRole.EMEND:
            info_layout.addWidget(QLabel("状态:"))
            self.status_combobox = QComboBox()
            self.status_combobox.addItems(
                [
                    AnnotationStatus.COMPLETED,
                    AnnotationStatus.REVIEWED,
                    AnnotationStatus.ERROR,
                ]
            )
            self.status_combobox.currentTextChanged.connect(self.on_status_changed)
            info_layout.addWidget(self.status_combobox)

        # 只有标注员可以保存标注
        if self.user_role == UserRole.ANNOTATOR:
            self.save_btn = QPushButton("保存标注")
            self.save_btn.clicked.connect(self.save_annotation)
            self.save_btn.setEnabled(False)
            info_layout.addWidget(self.save_btn)

        # 只有审核员可以保存审核
        if self.user_role == UserRole.EMEND:
            self.review_btn = QPushButton("保存审核")
            self.review_btn.clicked.connect(self.save_review)
            self.review_btn.setEnabled(False)
            info_layout.addWidget(self.review_btn)

        # Redis同步状态指示
        self.redis_status_label = QLabel("")
        info_layout.addWidget(self.redis_status_label)

        layout.addLayout(info_layout)

        splitter = QSplitter(Qt.Horizontal)

        left_frame = QFrame()
        left_frame.setFrameShape(QFrame.StyledPanel)
        left_layout = QVBoxLayout(left_frame)

        left_layout.addWidget(QLabel("标注列表:"))
        self.annotation_list = QListWidget()

        # 根据用户角色设置不同的右键菜单
        if self.user_role == UserRole.ANNOTATOR:
            self.annotation_list.setContextMenuPolicy(Qt.CustomContextMenu)
            self.annotation_list.customContextMenuRequested.connect(
                self.show_annotation_context_menu
            )

        left_layout.addWidget(self.annotation_list)

        # 只有标注员可以添加/删除标签
        if self.user_role == UserRole.ANNOTATOR:
            btn_layout = QHBoxLayout()
            self.add_label_btn = QPushButton("添加标签")
            self.add_label_btn.clicked.connect(self.add_label)
            self.delete_label_btn = QPushButton("删除标签")
            self.delete_label_btn.clicked.connect(self.delete_label)
            self.delete_label_btn.setEnabled(False)

            btn_layout.addWidget(self.add_label_btn)
            btn_layout.addWidget(self.delete_label_btn)
            left_layout.addLayout(btn_layout)

        splitter.addWidget(left_frame)

        right_frame = QFrame()
        right_frame.setFrameShape(QFrame.StyledPanel)
        right_layout = QVBoxLayout(right_frame)

        right_layout.addWidget(QLabel("标注详情:"))
        self.annotation_text = QTextEdit()
        self.annotation_text.setReadOnly(self.user_role != UserRole.ANNOTATOR)
        right_layout.addWidget(self.annotation_text)

        # 只有审核员可以查看审核历史
        if self.user_role == UserRole.EMEND:
            right_layout.addWidget(QLabel("审核历史:"))
            self.review_history = QTextBrowser()
            right_layout.addWidget(self.review_history)

        splitter.addWidget(right_frame)
        splitter.setSizes([300, 500])

        layout.addWidget(splitter)

    def connect_to_redis(self):
        """连接到Redis服务器"""
        # host, ok = QInputDialog.getText(self, "连接Redis", "主机:", text="localhost")
        # if not ok:
        #     return

        # port, ok = QInputDialog.getInt(self, "连接Redis", "端口:", value=6379)
        # if not ok:
        #     return

        # password, ok = QInputDialog.getText(self, "连接Redis", "密码:", QLineEdit.Password,text="123456")
        # if not ok:
        #     return

        # db, ok = QInputDialog.getInt(self, "连接Redis", "数据库:", value=0)
        # if not ok:
        #     return

        self.statusBar().showMessage("正在连接Redis服务器...")

        # self.redis.host = host
        # self.redis.port = port
        # self.redis.password = password
        # self.redis.db = db
        self.redis.connect()

    def on_redis_connected(self, success):
        """Redis连接成功处理"""
        if success:
            self.statusBar().showMessage("已连接到Redis服务器")
            self.redis_connection_available = True
            self.refresh_tabs()
        else:
            self.statusBar().showMessage("连接Redis失败 - 仅使用本地模式")
            self.redis_connection_available = False
            self.check_local_data()

    def on_connection_checked(self, connected):
        """连接状态检查处理"""
        if connected:
            self.redis_status_label.setText("(已连接Redis)")
            self.sync_status_label.setText("Redis已连接")
        else:
            self.redis_status_label.setText("(未连接Redis，更改将只保存到本地)")
            self.sync_status_label.setText("Redis未连接，点击同步按钮上传")

    def check_local_data(self):
        """检查本地数据"""
        local_tabs = self.local.get_local_tabs()
        if local_tabs:
            self.statusBar().showMessage(
                f"未连接Redis，使用本地数据 ({len(local_tabs)} 个tab)"
            )
            self.load_local_tabs(local_tabs)
        else:
            self.statusBar().showMessage("没有可用的本地数据，请先连接Redis或添加新Tab")

    def refresh_tabs(self):
        """刷新Tab列表"""
        local_tabs = self.local.get_local_tabs()

        if self.redis_connection_available:
            redis_tabs = self.redis.get_tabs()
            tabs = list(set(local_tabs + redis_tabs))
        else:
            tabs = local_tabs

        self.tab_combobox.clear()
        self.tab_combobox.addItems(tabs)

        if tabs:
            self.tab_combobox.setCurrentIndex(0)

    def load_local_tabs(self, tabs):
        """加载本地tab"""
        self.tab_combobox.clear()
        self.tab_combobox.addItems(tabs)
        if tabs:
            self.tab_combobox.setCurrentIndex(0)

    def on_tab_selected(self, tab_id):
        """Tab选择事件处理"""
        if not tab_id:
            return

        self.current_tab = tab_id
        self.refresh_files()

    def refresh_files(self):
        """刷新文件列表"""
        if not hasattr(self, "current_tab"):
            return

        self.file_table.setRowCount(0)

        status_filter = self.status_filter.currentText()
        if status_filter == "全部":
            status_filter = None

        # 根据用户角色筛选文件
        user_filter = self.username if self.user_role == UserRole.ANNOTATOR else None

        if self.redis_connection_available:
            files = self.redis.get_tab_files(
                self.current_tab, status=status_filter, user_id=user_filter
            )
            for i, filename in enumerate(files):
                file_data = self.redis.get_file_data(filename)
                if file_data:
                    self.add_file_to_table(filename, file_data, "Redis")
        else:
            files = self.local.get_tab_files(
                self.current_tab, status=status_filter, user_id=user_filter
            )
            for i, filename in enumerate(files):
                file_data = self.local.get_file_data(self.current_tab, filename)
                if file_data:
                    self.add_file_to_table(filename, file_data, "本地")

    def add_file_to_table(self, filename, file_data, source):
        """将文件添加到表格"""
        row = self.file_table.rowCount()
        self.file_table.insertRow(row)

        self.file_table.setItem(row, 0, QTableWidgetItem(filename))

        file_name = file_data.get("file_name", filename)
        self.file_table.setItem(row, 1, QTableWidgetItem(file_name))

        status = file_data.get("status", AnnotationStatus.UNASSIGNED)
        item = QTableWidgetItem(status)

        if status == AnnotationStatus.UNASSIGNED:
            item.setBackground(QColor(255, 255, 200))
        elif status == AnnotationStatus.ASSIGNED:
            item.setBackground(QColor(200, 255, 200))
        elif status == AnnotationStatus.COMPLETED:
            item.setBackground(QColor(200, 200, 255))
        elif status == AnnotationStatus.REVIEWED:
            item.setBackground(QColor(200, 255, 255))
        elif status == AnnotationStatus.ERROR:
            item.setBackground(QColor(255, 200, 200))

        self.file_table.setItem(row, 2, item)

        updatetime = file_data.get("updatetime", "")
        self.file_table.setItem(row, 3, QTableWidgetItem(updatetime))

        self.file_table.setItem(row, 4, QTableWidgetItem(source))

    def filter_files_by_status(self, status):
        """根据状态筛选文件"""
        self.refresh_files()

    def on_file_selected(self, row, column):
        """文件选择事件处理"""
        filename = self.file_table.item(row, 0).text()
        source = self.file_table.item(row, 4).text()

        if source == "Redis" and self.redis_connection_available:
            file_data = self.redis.get_file_data(filename)
        else:
            file_data = self.local.get_file_data(self.current_tab, filename)

        if not file_data:
            QMessageBox.warning(self, "错误", f"无法加载文件数据: {filename}")
            return

        self.current_filename = filename
        self.current_file_data = file_data
        self.current_file_source = source

        file_name = file_data.get("file_name", filename)
        status = file_data.get("status", AnnotationStatus.UNASSIGNED)
        updatetime = file_data.get("updatetime", "")

        self.file_info_label.setText(
            f"文件: {file_name} ({filename}) | 状态: {status} | 更新时间: {updatetime}"
        )

        # 根据用户角色设置状态下拉框
        if self.user_role == UserRole.ANNOTATOR:
            if status in [AnnotationStatus.ASSIGNED, AnnotationStatus.COMPLETED]:
                self.status_combobox.setCurrentText(status)
            else:
                self.status_combobox.setCurrentText(AnnotationStatus.ASSIGNED)
        elif self.user_role == UserRole.EMEND:
            if status in [
                AnnotationStatus.COMPLETED,
                AnnotationStatus.REVIEWED,
                AnnotationStatus.ERROR,
            ]:
                self.status_combobox.setCurrentText(status)
            else:
                self.status_combobox.setCurrentText(AnnotationStatus.COMPLETED)

        self.load_annotations(file_data)

        # 根据用户角色启用相应的按钮
        if self.user_role == UserRole.ANNOTATOR:
            self.save_btn.setEnabled(True)
        elif self.user_role == UserRole.EMEND:
            self.review_btn.setEnabled(True)

        # 如果是审核员，加载审核历史
        if self.user_role == UserRole.EMEND:
            self.load_review_history(file_data)

    def load_annotations(self, file_data):
        """加载标注信息"""
        self.annotation_list.clear()
        annotations = file_data.get("annotation_data", [])
        for i, annotation in enumerate(annotations):
            label = annotation.get("label", f"标注 {i+1}")
            annotator = annotation.get("annotator", "未知")
            time_str = annotation.get("annotation_time", "")
            item = QListWidgetItem(f"{label} (由 {annotator} 于 {time_str})")
            item.setData(Qt.UserRole, annotation)
            self.annotation_list.addItem(item)

        # 只有标注员可以删除标签
        if self.user_role == UserRole.ANNOTATOR:
            self.delete_label_btn.setEnabled(self.annotation_list.count() > 0)

    def load_review_history(self, file_data):
        """加载审核历史"""
        self.review_history.clear()
        reviews = file_data.get("reviews", [])
        if not reviews:
            self.review_history.setText("没有审核历史")
            return

        for review in reviews:
            reviewer = review.get("reviewer", "未知")
            status = review.get("status", "")
            comment = review.get("comment", "")
            time_str = review.get("review_time", "")

            self.review_history.append(f"<b>审核人:</b> {reviewer}")
            self.review_history.append(f"<b>状态:</b> {status}")
            self.review_history.append(f"<b>时间:</b> {time_str}")
            self.review_history.append(f"<b>评论:</b> {comment}")
            self.review_history.append("-" * 50)

    def on_status_changed(self, status):
        """状态变更处理"""
        if hasattr(self, "current_file_data"):
            self.current_file_data["status"] = status

    def save_annotation(self):
        """保存标注数据"""
        if not hasattr(self, "current_filename"):
            return

        # 更新标注时间
        self.current_file_data["updatetime"] = datetime.now().isoformat()

        # 保存到本地
        local_success = self.local.save_file_data(
            self.current_tab, self.current_filename, self.current_file_data
        )

        # 如果Redis连接可用，同时保存到Redis
        redis_success = False
        if self.redis_connection_available:
            redis_success = self.redis.save_file_data(
                self.current_filename, self.current_file_data
            )
            if redis_success:
                self.redis.add_file_to_tab(self.current_tab, self.current_filename)

        # 显示保存结果
        if local_success and redis_success:
            self.statusBar().showMessage(
                f"已保存到本地和Redis: {self.current_filename}"
            )
        elif local_success:
            self.statusBar().showMessage(
                f"已保存到本地 (Redis未连接): {self.current_filename}"
            )
            self.sync_status_label.setText("有未同步的本地更改，点击同步按钮上传")
        else:
            self.statusBar().showMessage(f"保存失败: {self.current_filename}")
        self.refresh_files()

    def save_review(self):
        """保存审核数据"""
        if not hasattr(self, "current_filename"):
            return

        dialog = AddReviewDialog(
            self, self.current_file_data.get("status", AnnotationStatus.COMPLETED)
        )
        if dialog.exec_():
            review_data = dialog.get_review_data()

            # 更新文件状态
            self.current_file_data["status"] = review_data["status"]

            # 添加审核记录
            if "reviews" not in self.current_file_data:
                self.current_file_data["reviews"] = []

            self.current_file_data["reviews"].append(
                {
                    "reviewer": self.username,
                    "status": review_data["status"],
                    "comment": review_data["comment"],
                    "review_time": datetime.now().isoformat(),
                }
            )

            # 更新时间
            self.current_file_data["updatetime"] = datetime.now().isoformat()

            # 保存到本地
            local_success = self.local.save_file_data(
                self.current_tab, self.current_filename, self.current_file_data
            )

            # 如果Redis连接可用，同时保存到Redis
            redis_success = False
            if self.redis_connection_available:
                redis_success = self.redis.save_file_data(
                    self.current_filename, self.current_file_data
                )

            # 显示保存结果
            if local_success and redis_success:
                self.statusBar().showMessage(
                    f"已保存审核到本地和Redis: {self.current_filename}"
                )
            elif local_success:
                self.statusBar().showMessage(
                    f"已保存审核到本地 (Redis未连接): {self.current_filename}"
                )
            else:
                self.statusBar().showMessage(f"保存审核失败: {self.current_filename}")

            self.refresh_files()
            self.load_review_history(self.current_file_data)

    def sync_data(self):
        """同步本地和Redis数据"""
        if not hasattr(self, "current_tab"):
            QMessageBox.information(self, "提示", "请先选择一个Tab")
            return

        dialog = QDialog(self)
        dialog.setWindowTitle("数据同步")
        dialog.setMinimumWidth(400)
        layout = QVBoxLayout(dialog)

        progress_label = QLabel("准备同步...")
        layout.addWidget(progress_label)

        progress_bar = QProgressBar()
        progress_bar.setValue(0)
        layout.addWidget(progress_bar)

        sync_log = QTextEdit()
        sync_log.setReadOnly(True)
        layout.addWidget(sync_log)

        cancel_btn = QPushButton("取消")
        cancel_btn.clicked.connect(dialog.reject)
        layout.addWidget(cancel_btn)

        self.sync_worker = DataSyncWorker(self.username, self.redis, self.local)
        self.sync_worker.set_tabs_to_sync([self.current_tab])

        self.sync_worker.sync_progress.connect(progress_bar.setValue)
        self.sync_worker.file_synced.connect(
            lambda filename, status: sync_log.append(f"{filename}: {status}")
        )
        self.sync_worker.sync_complete.connect(
            lambda success, msg: progress_label.setText(f"同步完成: {msg}")
        )
        self.sync_worker.sync_complete.connect(dialog.accept)
        cancel_btn.clicked.connect(self.sync_worker.stop_sync)

        self.sync_worker.start()
        dialog.exec_()
        if self.sync_worker.isFinished():
            self.refresh_files()
            self.sync_status_label.setText("数据已同步")

    def add_new_tab(self):
        """添加新Tab并生成模拟数据"""
        if self.user_role != UserRole.ADMIN:
            QMessageBox.warning(self, "权限不足", "只有管理员可以添加新Tab")
            return

        tab_name, ok = QInputDialog.getText(self, "添加新Tab", "请输入Tab名称:")
        if not ok or not tab_name:
            return

        # 检查Tab是否已存在
        local_tabs = self.local.get_local_tabs()
        if self.redis_connection_available:
            redis_tabs = self.redis.get_tabs()
            tabs = list(set(local_tabs + redis_tabs))
            if tab_name in tabs:
                QMessageBox.warning(self, "错误", f"Tab '{tab_name}' 已存在")
                return

        # 添加到本地
        if self.local.add_local_tab(tab_name):
            QMessageBox.information(self, "成功", f"已在本地添加Tab: {tab_name}")

            # 如果已连接Redis，也添加到Redis
            if self.redis_connection_available:
                if self.redis.add_user_tab(self.username, tab_name):
                    QMessageBox.information(
                        self, "成功", f"已在Redis添加Tab: {tab_name}"
                    )
                else:
                    QMessageBox.warning(
                        self, "警告", f"在Redis添加Tab失败，但已在本地添加"
                    )

            # 刷新Tab列表
            self.refresh_tabs()
            self.tab_combobox.setCurrentText(tab_name)
        else:
            QMessageBox.warning(self, "错误", f"添加Tab '{tab_name}' 失败")

    def add_random_files(self):
        """添加随机文件"""
        if self.user_role != UserRole.ADMIN:
            QMessageBox.warning(self, "权限不足", "只有管理员可以添加文件")
            return

        if not hasattr(self, "current_tab"):
            QMessageBox.information(self, "提示", "请先选择一个Tab")
            return

        dialog = AddFilesDialog(self)
        if dialog.exec_():
            options = dialog.get_options()
            file_count = options["file_count"]
            status_options = options["status_options"]

            if not status_options:
                QMessageBox.warning(self, "错误", "请至少选择一种状态")
                return

            progress_dialog = QDialog(self)
            progress_dialog.setWindowTitle("添加文件")
            progress_dialog.setMinimumWidth(400)
            layout = QVBoxLayout(progress_dialog)

            progress_label = QLabel(f"正在添加 {file_count} 个文件...")
            layout.addWidget(progress_label)

            progress_bar = QProgressBar()
            progress_bar.setRange(0, file_count)
            progress_bar.setValue(0)
            layout.addWidget(progress_bar)

            QTimer.singleShot(
                0,
                lambda: self._add_files(
                    file_count, status_options, progress_bar, progress_dialog
                ),
            )

            progress_dialog.exec_()
            self.refresh_files()

    def _add_files(self, file_count, status_options, progress_bar, progress_dialog):
        """实际添加文件的方法"""
        for i in range(file_count):
            filename = f"random_file_{uuid.uuid4().hex[:8]}"
            file_name = f"随机文件_{i+1}.jpg"

            # 随机选择一种状态
            status = random.choice(status_options)

            # 生成文件数据
            file_data = {
                "filename": filename,
                "file_name": file_name,
                "file_path": f"/path/to/{file_name}",
                "owner": self.username if status == AnnotationStatus.ASSIGNED else "",
                "status": status,
                "updatetime": datetime.now().isoformat(),
                # "annotation_data": self.generate_sample_annotations(i+1) if status != AnnotationStatus.UNASSIGNED else []
            }

            # 保存到本地
            self.local.save_file_data(self.current_tab, filename, file_data)

            # 如果已连接Redis，也保存到Redis
            if self.redis_connection_available:
                self.redis.save_file_data(filename, file_data)
                self.redis.add_file_to_tab(self.current_tab, filename)

            progress_bar.setValue(i + 1)
            QApplication.processEvents()

        progress_dialog.accept()
        QMessageBox.information(
            self, "成功", f"已成功添加 {file_count} 个文件到Tab: {self.current_tab}"
        )

    def delete_current_tab(self):
        """删除当前Tab"""
        if self.user_role != UserRole.ADMIN:
            QMessageBox.warning(self, "权限不足", "只有管理员可以删除Tab")
            return

        if not hasattr(self, "current_tab"):
            QMessageBox.information(self, "提示", "请先选择一个Tab")
            return

        current_tab = self.current_tab

        reply = QMessageBox.question(
            self,
            "确认删除",
            f"确定要删除Tab '{current_tab}' 吗？这将删除该Tab下的所有文件，且无法恢复。",
            QMessageBox.Yes | QMessageBox.No,
            QMessageBox.No,
        )

        if reply == QMessageBox.Yes:
            # 删除本地数据
            local_success = self.local.delete_local_tab(current_tab)

            # 删除Redis数据
            redis_success = False
            if self.redis_connection_available:
                redis_success = self.redis.delete_tab(self.username, current_tab)

            if local_success and (not self.redis_connection_available or redis_success):
                QMessageBox.information(self, "成功", f"已成功删除Tab: {current_tab}")
                self.refresh_tabs()
                if self.tab_combobox.count() > 0:
                    self.tab_combobox.setCurrentIndex(0)
                else:
                    self.current_tab = None
            else:
                QMessageBox.warning(self, "错误", f"删除Tab失败: {current_tab}")

    def assign_files(self):
        """分配文件给自己"""
        if self.user_role != UserRole.ANNOTATOR:
            QMessageBox.warning(self, "权限不足", "只有标注员可以分配文件")
            return

        if not hasattr(self, "current_tab"):
            QMessageBox.information(self, "提示", "请先选择一个Tab")
            return

        # 获取未分配的文件
        if self.redis_connection_available:
            unassigned_files = self.redis.get_unassigned_files(self.current_tab)
        else:
            unassigned_files = self.local.get_unassigned_files(self.current_tab)

        if not unassigned_files:
            QMessageBox.information(self, "提示", "没有可分配的未标注文件")
            return

        dialog = AssignFilesDialog(
            self,
            unassigned_files=unassigned_files,
            tab_id=self.current_tab,
            user_id=self.username,
            redis_connector=self.redis,
            local_manager=self.local,
            redis_available=self.redis_connection_available,
        )
        dialog.exec_()
        self.refresh_files()

    def add_review(self):
        """添加审核"""
        if self.user_role != UserRole.EMEND:
            QMessageBox.warning(self, "权限不足", "只有审核员可以添加审核")
            return

        if not hasattr(self, "current_filename"):
            QMessageBox.information(self, "提示", "请先选择一个文件")
            return

        # 确保选择的是已完成的文件
        if self.current_file_data.get("status") != AnnotationStatus.COMPLETED:
            QMessageBox.information(
                self, "提示", "请选择状态为'标注完成'的文件进行审核"
            )
            return

        self.save_review()

    def show_annotation_context_menu(self, position):
        """显示标注上下文菜单"""
        if not self.annotation_list.selectedItems():
            return

        menu = QMenu()
        edit_action = QAction("编辑标签", self)
        edit_action.triggered.connect(self.edit_label)
        menu.addAction(edit_action)

        delete_action = QAction("删除标签", self)
        delete_action.triggered.connect(self.delete_label)
        menu.addAction(delete_action)

        menu.exec_(self.annotation_list.mapToGlobal(position))

    def add_label(self):
        """添加标签"""
        if not hasattr(self, "current_file_data"):
            return

        dialog = EditLabelDialog(self, "新标签")
        if dialog.exec_():
            label_data = dialog.get_label_data()
            if label_data:
                if "annotation_data" not in self.current_file_data:
                    self.current_file_data["annotation_data"] = []

                annotation_id = f"anno_{uuid.uuid4().hex[:8]}"
                new_annotation = {
                    "annotation_id": annotation_id,
                    "annotator": self.username,
                    "annotation_time": datetime.now().isoformat(),
                    **label_data,
                }

                self.current_file_data["annotation_data"].append(new_annotation)
                self.load_annotations(self.current_file_data)

    def edit_label(self):
        """编辑标签"""
        if (
            not hasattr(self, "current_file_data")
            or not self.annotation_list.selectedItems()
        ):
            return

        selected_item = self.annotation_list.selectedItems()[0]
        annotation = selected_item.data(Qt.UserRole)

        dialog = EditLabelDialog(
            self,
            current_label=annotation.get("label", ""),
            coordinates=annotation.get("coordinates", {}),
        )

        if dialog.exec_():
            label_data = dialog.get_label_data()
            if label_data:
                annotation.update(label_data)
                annotation["updatetime"] = datetime.now().isoformat()
                self.load_annotations(self.current_file_data)

    def delete_label(self):
        """删除标签"""
        if (
            not hasattr(self, "current_file_data")
            or not self.annotation_list.selectedItems()
        ):
            return

        selected_item = self.annotation_list.selectedItems()[0]
        annotation = selected_item.data(Qt.UserRole)

        reply = QMessageBox.question(
            self,
            "确认删除",
            f"确定要删除标签 '{annotation.get('label', '未知')}' 吗？",
            QMessageBox.Yes | QMessageBox.No,
            QMessageBox.No,
        )

        if reply == QMessageBox.Yes:
            if "annotation_data" in self.current_file_data:
                self.current_file_data["annotation_data"] = [
                    a
                    for a in self.current_file_data["annotation_data"]
                    if a.get("annotation_id") != annotation.get("annotation_id")
                ]

            self.load_annotations(self.current_file_data)

    def generate_sample_annotations(self, file_index):
        """生成模拟标注数据"""
        annotations = []
        # 为每个文件生成1-3个标注
        for i in range(1, file_index + 2):
            annotation = {
                "annotation_id": f"anno_{file_index}_{i}",
                "label": self.get_sample_label(file_index, i),
                "coordinates": {
                    "x": 100 + i * 50,
                    "y": 100 + i * 30,
                    "width": 150 - i * 20,
                    "height": 100 - i * 10,
                },
                "annotation_time": (
                    datetime.now() - timedelta(hours=file_index + i)
                ).isoformat(),
                "annotator": self.username,
                "confidence": round(0.8 + i * 0.1, 2),
                "comments": [f"这是示例标注 {i} 的评论"],
                "metadata": {
                    "color": self.get_sample_color(i),
                    "size": self.get_sample_size(i),
                },
            }
            annotations.append(annotation)
        return annotations

    def get_sample_label(self, file_index, anno_index):
        """获取示例标签"""
        labels = ["行人", "汽车", "自行车", "公交车", "摩托车", "卡车"]
        return labels[(file_index + anno_index) % len(labels)]

    def get_sample_color(self, index):
        """获取示例颜色"""
        colors = ["红色", "蓝色", "绿色", "黄色", "黑色", "白色"]
        return colors[index % len(colors)]

    def get_sample_size(self, index):
        """获取示例大小"""
        sizes = ["小", "中", "大"]
        return sizes[index % len(sizes)]

    def on_error(self, error_msg):
        """错误处理"""
        self.statusBar().showMessage(f"错误: {error_msg}")
        QMessageBox.critical(self, "错误", error_msg)


if __name__ == "__main__":
    app = QApplication(sys.argv)

    # 获取用户ID和角色
    user_id, ok = QInputDialog.getText(None, "用户登录", "请输入用户ID:", text="admin")
    if not ok or not user_id:
        sys.exit(0)

    # 模拟角色选择（实际应用中应从认证系统获取）
    role_options = [UserRole.ANNOTATOR, UserRole.EMEND, UserRole.ADMIN]
    role_text, ok = QInputDialog.getItem(
        None, "选择角色", "请选择用户角色:", role_options, 0, False  # 默认选择标注员
    )
    role_index = role_options.index(role_text)
    if not ok or role_index == -1:
        sys.exit(0)

    user_role = role_options[role_index]

    window = AnnotationApp(user_id, user_role)
    window.show()
    sys.exit(app.exec())
